/* UTF8 Encoding */
/*-----------------------------------------------------------------------
|                            FILE DESCRIPTION                           |
-----------------------------------------------------------------------*/
/*----------------------------------------------------------------------
  - File name     : eGUI.hpp
  - Author        : liuzhihua (liuzhihuawy@163.com)
  - Update date   : 
  -	Brief         : Easy GUI Framework
  - Version       : v0.1
-----------------------------------------------------------------------*/
/*-----------------------------------------------------------------------
|                               UPDATE NOTE                             |
-----------------------------------------------------------------------*/
/**
  * Update note:
  * ------------   ---------------   ----------------------------------
  *     Date            Author                      Note
  * ------------   ---------------   ----------------------------------
  *  2023.05.24       liuzhihua                  Create file          
***/

#ifndef EGUI_H_
#define EGUI_H_


/*-----------------------------------------------------------------------
|                               INCLUDES                                |
-----------------------------------------------------------------------*/
#include "stm32_adafruit_lcd.h"
#include "FreeRTOS.h"
#include "elist.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include "eGUI_App.hpp"
#include "log.h"
#include "eString.hpp"
/*-----------------------------------------------------------------------
|                                DEFINES                                |
-----------------------------------------------------------------------*/
#define eGUI_Assert(x) do{if (!(x)) {while(1);};}while(0)
#define eGUI_LOG(str, ...) LOG_ERR("eGUI", str, ##__VA_ARGS__)
#define eGUI_WriteLine(x, y, str) BSP_LCD_DisplayStringAtLine(y, str)
#define eGUI_PrintLine(x, y, str, ...) BSP_LCD_PrintLine(y, str, ##__VA_ARGS__)
#define eGUI_DrawRect(x,y,w,h,fill) \
        do{fill > 0 ? BSP_LCD_DrawRect(x,y,w,h) : BSP_LCD_FillRect(x,y,w,h);}while (0)
#define eGUI_DrawPixel(x,y,color) BSP_LCD_DrawPixel(x,y,color)

#define eGUI_Free vPortFree
static inline void* eGUI_Malloc(size_t size)
{
    void* ptr = pvPortMalloc(size);
    if (ptr == NULL)
    {
        eGUI_LOG("eGUI Malloc Failed\n");
    }
    return ptr;
}

typedef enum 
{
    eGUI_OK,
    eGUI_ERROR,
    eGUI_PERROR, //param error
}eGUI_State;

enum eGUI_PAGE_TYPE
{
    eGUI_PAGE_TYPE_TEXTLIST,
    eGUI_PAGE_TYPE_GRAPH,
    eGUI_PAGE_TYPE_INPUTBOX,
    eGUI_PAGE_TYPE_NULL = 0xFFU
};


enum eGUI_ItemString_Type{
    ITEM_STRING_TYPE_CCHAR,   //const char
    ITEM_STRING_TYPE_VCHAR,   //variable char
};

typedef struct
{
    uint8_t type;
    union StringItem
    {
        const char *pStr;
        eString *eStr;
    }u;
    void (*draw)(char *, size_t);
    void (*click)(uint32_t);
}eGUI_ItemString;

const int itemstringLength = sizeof(eGUI_ItemString);
class eGUI_Page;

class eGUI
{
    private:
        list_t pageStack;
        void clean(void);

    public:
        eGUI_Page *page;
        void (*ctrlcb)(uint32_t);
        uint8_t cleanFlag;
        static const uint8_t xFontSize = 17;
        static const uint8_t yFontSize = 24;
        static const uint16_t xPixel = 480;
        static const uint16_t yPixel = 320;
        static const uint16_t textRows = yPixel/yFontSize;    //行数
        static const uint16_t textColumns = xPixel/xFontSize; //列数
        uint8_t textBuffer[textRows][textColumns];


        eGUI(void);
        void run(void);
        void input(uint32_t args);
        eGUI_State jumpPush(eGUI_Page *Page, bool clear = false);
        eGUI_State jumpPush(eGUI_Page *Page, void(*Ctrlcb)(uint32_t), bool clear = false);
        eGUI_State jumpPop(bool clear = false);
};
extern eGUI egui;

class eGUI_TextList
{
    public:
        void * operator new(std::size_t size);
        void operator delete(void* ptr);
        eGUI_TextList(eGUI &egui);
        ~eGUI_TextList(void);

        eGUI *root;
        list_t *itemList;
        int16_t cursor;
        uint16_t topTextIndex;
        bool scrollBarReflashFlag;
        bool cursorDisplayFlag;
        uint16_t getItemSelect(void);
        eGUI_ItemString *getItemString(void);
        eGUI_ItemString *getItemString(uint16_t Index);
        eGUI_State addItemString(const char *pstr,
                                 uint8_t type,
                                 void (*draw)(char *, size_t),
                                 void (*click)(uint32_t));

        void deleteItemString(int index);
        eGUI_TextList& operator<<(const char *cstr);

        eGUI_TextList& operator<<(char *vstr);

        eGUI_TextList& operator<<(eString &estr);

        eGUI_TextList& operator<<(void (*draw)(char *, size_t));

        eGUI_TextList& operator<<(void (*click)(uint32_t));

        void drawScrollBar(void);

        void drawCursor(int row);
        void show(void);

        eGUI_State movePage(int num);
        eGUI_State moveCursor(int num);

};

class eGUI_Graph
{
    public:
        void * operator new(std::size_t size)
        {
            return eGUI_Malloc(size);
        }
        void operator delete(void* ptr)
        {
            if (ptr)
                eGUI_Free(ptr);
        }
        eGUI *root;
        bool autoReflash;
        uint16_t *imageSrc;
        uint16_t xPos, yPos, xSize, ySize;

#pragma push
#pragma O0
        eGUI_Graph(eGUI &egui)
        {
            if (this == NULL) return;
            this->root = &egui;
        }
        eGUI_Graph(eGUI &egui, bool AutoReflash,
                    uint16_t XPos, uint16_t YPos,
                    uint16_t *imgSrc, uint16_t XSize, uint16_t YSize)
        {
            if (this == NULL) return;
            this->root = &egui;
            this->autoReflash = AutoReflash;
            this->xPos = XPos;
            this->yPos = YPos;
            this->imageSrc = imgSrc;
            this->xSize = XSize;
            this->ySize = YSize;
        }
#pragma pop


};
// ProgressBar ScrollBar Slider CheckBox MessageBox
class eGUI_InputBox
{
    private:
        static const int frequnce = 12;
        uint8_t tCnt;
    public:
        void * operator new(std::size_t size)
        {
            return eGUI_Malloc(size);
        }
        void operator delete(void* ptr)
        {
            if (ptr)
                eGUI_Free(ptr);
        }
        void *bindData;
        typedef enum{
            NULL_DATA,
            BOOL_DATA,
            INT8_DATA,
            INT16_DATA,
            INT32_DATA,
            INT64_DATA,
            UINT8_DATA,
            UINT16_DATA,
            UINT32_DATA,
            UINT64_DATA,
            F32_DATA,
            F64_DATA
        }BindDataType;
        BindDataType type;
        eString text;
        const char *formatString;
        int row;
        int cursor;
        eGUI_InputBox(int Row, void* BindData, BindDataType Type, const char *FmtStr)
        {
            if (this == NULL) return;
            setInputBox(Row, BindData, Type, FmtStr);
        }

        void setInputBox(int Row, void* BindData, BindDataType Type, const char *FmtStr)
        {
            row = Row;
            bindData = BindData;
            type = Type;
            formatString = FmtStr;
            syncString();
            text += "_";
            cursor = text.length() - 1;
        }

        void syncString(void)
        {
            switch (type)
            {
            case BOOL_DATA:
                text = text.format(formatString, *(uint32_t*)bindData);
                break;
            case INT64_DATA:
                text = text.format(formatString, *(int32_t*)bindData);
                break;
            case UINT64_DATA:
                text = text.format(formatString, *(uint64_t*)bindData);
                break;
            case F32_DATA:
                text = text.format(formatString, *(float*)bindData);
                break;
            case F64_DATA:
                text = text.format(formatString, *(double*)bindData);
                break;
            default:
                if (type >= INT8_DATA && type <= INT32_DATA)
                {
                    text = text.format(formatString, *(int32_t*)bindData);
                }
                else if (type >= UINT8_DATA && type <= UINT32_DATA)
                {
                    text = text.format(formatString, *(uint32_t*)bindData);
                }
                break;
            }
        }
        double getFdata(void)
        {
            text.erase(cursor, 1);
            return atof(text.c_str());
        }
        uint32_t getUIdata(void)
        {
            text.erase(cursor, 1);
            return (uint32_t)atoi(text.c_str());
        }
        int getIdata(void)
        {
            text.erase(cursor, 1);
            return atoi(text.c_str());
        }
        uint64_t getULdata(void)
        {
            text.erase(cursor, 1);
            return (uint64_t)atoll(text.c_str());
        }
        long long getLdata(void)
        {
            text.erase(cursor, 1);
            return atoll(text.c_str());
        }

        void draw(void)
        {
            tCnt++;
            if (tCnt <= frequnce/2)
            {
                text[cursor] = '_';
            }
            else if (tCnt == frequnce)
            {
                tCnt = 0;
            }
            else
            {
                text[cursor] = ' ';
            }
            BSP_LCD_SetTextColor(LCD_COLOR_BLACK);
            BSP_LCD_SetBackColor(0xC618);
            eGUI_WriteLine(0, row, (uint8_t*)text.c_str());
        }
        void ctrlcb(uint32_t key)
        {
            switch (key)
            {
            case 'l':
                if (cursor > 0)
                {
                    text[cursor] = text[cursor-1];
                    text[cursor-1] = '_';
                    cursor--;
                }
                break;
            case 'r':
                if (cursor < egui.xFontSize && text.length()-1 > cursor)
                {
                    text[cursor] = text[cursor+1];
                    text[cursor+1] = '_';
                    cursor++;
                }
                break;
            case 'D':
                text.insert(cursor++, '.');
                break;
            case 'E':
                text.insert(cursor++, '-');
                break;
            case 0x01:
                if (cursor < 1) return;
                text.erase(--cursor, 1);
                egui.cleanFlag = 1;
                break;
            case 0x04:
                syncData();
                egui.jumpPop(true);
                break;
            default:
                if (key >= '0' && key <= '9')
                {
                    text.insert(cursor++, key);
                }
                break;
            }
        }

        //绑定数据
        void bind(void *pData, BindDataType Type)
        {
            if (Type > F64_DATA) return;
            type = Type;
            bindData = pData;
        }

        void syncData(void)
        {
            switch (type)
            {
            case eGUI_InputBox::BOOL_DATA:
                *(bool*)bindData = (getIdata() == 0 ? false : true);
                break;
            case eGUI_InputBox::INT8_DATA:
                *(int8_t*)bindData = (int8_t)getIdata();
                break;
            case eGUI_InputBox::INT16_DATA:
                *(int16_t*)bindData = (int16_t)getIdata();
                break;
            case eGUI_InputBox::INT32_DATA:
                *(int32_t*)bindData = (int32_t)getIdata();
                break;
            case eGUI_InputBox::INT64_DATA:
                *(int64_t*)bindData = (int64_t)getLdata();
                break;
            case eGUI_InputBox::UINT8_DATA:
                *(uint8_t*)bindData = (uint8_t)getUIdata();
                break;
            case eGUI_InputBox::UINT16_DATA:
                *(uint16_t*)bindData = (uint16_t)getUIdata();
                break;
            case eGUI_InputBox::UINT32_DATA:
                *(uint32_t*)bindData = (uint32_t)getUIdata();
                break;
            case eGUI_InputBox::UINT64_DATA:
                *(uint64_t*)bindData = (uint64_t)getULdata();
                break;
            case eGUI_InputBox::F32_DATA:
                *(float*)bindData = (float)getFdata();
                break;
            case eGUI_InputBox::F64_DATA:
                *(double*)bindData = (double)getFdata();
                break;
            default:
                eGUI_LOG("Data type error,addr:%p,type:%d\n", bindData, type);
                break;
            }
        }
};


typedef union eGUI_Page_Type_Item  eGUI_Page_Type_Item;
union eGUI_Page_Type_Item
{
    eGUI_TextList *textList;
    eGUI_Graph *graph;
    eGUI_InputBox *inputBox;
};
class eGUI_Page
{
    public:
        void * operator new(std::size_t size);
        void operator delete(void* ptr);
        eGUI_PAGE_TYPE type;
        eGUI_Page_Type_Item u;
        void (*viewcb)(eGUI_Page*);
        void (*ctrlcb)(uint32_t);

        eGUI_Page(eGUI_PAGE_TYPE Type, void (*Ctrlcb)(uint32_t), void (*Viewcb)(eGUI_Page*) = NULL);
        eGUI_Page(eGUI_Graph *eGUI_Graph, void (*Ctrlcb)(uint32_t), void (*Viewcb)(eGUI_Page*));
        eGUI_Page(eGUI_InputBox *eGUI_InputBox);
        void autoFlashOpen(void);
        void autoFlashClose(void);
        bool autoFlash;
    private:
};




/*-----------------------------------------------------------------------
|                                  API                                  |
-----------------------------------------------------------------------*/



#endif
